/*
 * Created on Feb 13, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.query.engine;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;

import edu.mit.simile.longwell.Cache;
import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.query.IIndexer;
import edu.mit.simile.longwell.query.Order;
import edu.mit.simile.longwell.query.QueryException;
import edu.mit.simile.longwell.query.project.IDateTimeProjection;
import edu.mit.simile.longwell.query.project.IIntegerProjection;
import edu.mit.simile.longwell.query.project.IProjection;
import edu.mit.simile.longwell.query.project.IProjector;
import edu.mit.simile.longwell.schema.ISchemaModel;

/**
 * @author dfhuynh
 */
public class Indexer implements IIndexer {
	final protected Profile 	m_profile;
	final protected QueryEngine	m_engine;
	final protected Cache		m_results = 
									new Cache(10, Indexer.class, "results");
	
	Indexer(Profile profile, QueryEngine engine) {
		m_profile = profile;
		m_engine = engine;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.IIndexer#index(java.util.Set, java.util.List, java.util.List, int, java.lang.String, boolean)
	 */
	public List index(Set objects, List sortedObjects, List orders,
			int itemsPerPage, String locale, boolean fresh)
			throws QueryException {
		
		List results = (List) m_results.get(sortedObjects);
		if (results == null || fresh) {
			results = internalIndex(objects, sortedObjects, orders, itemsPerPage, locale);
			m_results.put(sortedObjects, results);
		}
		return results;
	}
	
	protected List internalIndex(
		Set objects, List sortedObjects, List orders, 
		int itemsPerPage, String locale) throws QueryException {
		
		List entries = new ArrayList();
		if (orders.size() > 0) {
			try {
				Order order = (Order) orders.get(0);
				
				IProjector projector = m_engine.getProjectorManager().getProjector(
					order.m_projectorName, order.m_projectorParameter, locale);
				IProjection projection = projector.project(objects);
				
				if (projection instanceof IIntegerProjection) {
					indexIntegers(
						sortedObjects, (IIntegerProjection) projection,
						itemsPerPage, order.m_ascending, locale, entries);
				} else if (projection instanceof IDateTimeProjection) {
					indexDates(
						sortedObjects, (IDateTimeProjection) projection,
						itemsPerPage, order.m_ascending, locale, entries);
				} else {
					indexStrings(
						sortedObjects, projection,
						itemsPerPage, locale, entries);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return entries;
	}
	
	protected void indexIntegers(
		List objects, IIntegerProjection projection, 
		int itemsPerPage, boolean ascending, String locale, List entries) {

		int min = projection.getMin();
		int max = projection.getMax();
		int diff = max - min;
		
		int interval = 10;
		while (diff / interval > 10) {
			interval *= 10;
		}
		if (diff / interval < 2) {
			interval /= 10;
		}
		
		min = (min / interval) * interval;
		max = (1 + max / interval) * interval;

		int intervalCount = (max - min) / interval;
		int soFar = ascending ? 
			objects.size() - projection.countObjects(min, Integer.MAX_VALUE) :
			projection.countObjects(max, Integer.MAX_VALUE);
		
		int start = ascending ? 0 : intervalCount - 1;
		int stop = ascending ? intervalCount : -1;
		int change = ascending ? 1 : -1;
		
		for (int j = start; j != stop; j += change) {
			int from = (min + j * interval);
			int to = (min + j * interval + (interval > 1 ? interval - 1 : interval));
			int count = projection.countObjects(from, to);
			
			if (count > 0) {
				entries.add(new Entry(
					Integer.toString(from),
					soFar / itemsPerPage,
					count
				));
				
				soFar += count;
			}
		}
	}
	
	final static protected long	s_day = 24 * 60 * 60 * 1000;
	final static protected long s_month = s_day * 30;
	final static protected long s_year = s_day * 365;
	final static protected long s_decade = s_year * 10;
	final static protected long s_century = s_year * 100;
	
	protected void indexDates(
		List objects, IDateTimeProjection projection, 
		int itemsPerPage, boolean ascending, String locale, List entries) {
		
		Date earliest = projection.getEarliest();
		Date latest = projection.getLatest();
		long diff = latest.getTime() - earliest.getTime();
		
		Calendar earliestC = Calendar.getInstance(); earliestC.clear();
		Calendar latestC = Calendar.getInstance(); latestC.clear();
		
		if (diff > s_century * 5) {
			earliestC.set(
				earliest.getYear() - (earliest.getYear() % 100), Calendar.YEAR);
			
			latestC.set(
				latest.getYear() - (latest.getYear() % 100) + 100, Calendar.YEAR);
			
			indexPeriods(
				objects, projection, itemsPerPage,
				earliestC, latestC, ascending, locale, entries,
				Calendar.YEAR, 100, "yyyy");
		} else if (diff > s_decade * 5) {
			earliestC.set(
				earliest.getYear() - (earliest.getYear() % 10), Calendar.YEAR);
			
			latestC.set(
				latest.getYear() - (latest.getYear() % 10) + 10, Calendar.YEAR);
			
			indexPeriods(
				objects, projection, itemsPerPage,
				earliestC, latestC, ascending, locale, entries,
				Calendar.YEAR, 10, "yyyy");
		} else if (diff > s_year * 5) {
			earliestC.set(earliest.getYear(), Calendar.YEAR);
			latestC.set(latest.getYear() + 1, Calendar.YEAR);
			
			indexPeriods(
				objects, projection, itemsPerPage,
				earliestC, latestC, ascending, locale, entries,
				Calendar.YEAR, 1, "yyyy");
		} else if (diff > s_month * 5) {
			earliestC.set(earliest.getYear(), Calendar.YEAR);
			earliestC.set(earliest.getMonth(), Calendar.MONTH);
			latestC.set(latest.getYear() + 1, Calendar.YEAR);
			
			indexPeriods(
				objects, projection, itemsPerPage,
				earliestC, latestC, ascending, locale, entries,
				Calendar.MONTH, 1, "MMM yyyy");
		} else {
			earliestC.setTime(earliest);
			earliestC.clear(Calendar.HOUR_OF_DAY);
			earliestC.clear(Calendar.MINUTE);
			earliestC.clear(Calendar.SECOND);
			earliestC.clear(Calendar.MILLISECOND);
			latestC.setTime(latest);
			
			indexPeriods(
				objects, projection, itemsPerPage,
				earliestC, latestC, ascending, locale, entries,
				Calendar.DATE, 1, null);
		}
	}
	
	protected void indexPeriods(
		List 				objects, 
		IDateTimeProjection projection,
		int 				itemsPerPage,
		Calendar			earliest,
		Calendar			latest,
		boolean 			ascending,
		String 				locale,
		List 				entries,
		int					field,
		int					change,
		String				format
	) {
		DateFormat sdf = format != null ? 
			new SimpleDateFormat(format) :
			DateFormat.getDateInstance(DateFormat.SHORT);
		
		int total = objects.size();
		int soFar = ascending ?
			objects.size() - projection.countObjects(null, earliest.getTime()) :
			projection.countObjects(latest.getTime(), null);
			
		while (earliest.before(latest) && soFar < total) {
			Date start, end;
			if (ascending) {
				start = (Date) earliest.getTime().clone();
				earliest.add(field, change);
				end = earliest.getTime();
			} else {
				end = (Date) latest.getTime().clone();
				latest.add(field, -change);
				start = latest.getTime();
			}
			
			int count = projection.countObjects(start, end);
			if (count > 0) {
				entries.add(new Entry(
					sdf.format(start),
					soFar / itemsPerPage,
					count
				));
				
				soFar += count;
			}
		}
	}
	
	protected void indexStrings(
		List objects, IProjection projection, 
		int itemsPerPage, String locale, List entries) {
		
		int pageCount = objects.size() / itemsPerPage;
		
		indexStrings2(objects, projection, itemsPerPage, locale, entries, 1);
		if (entries.size() < pageCount / 3 && entries.size() < 30) {
			entries.clear();
			indexStrings2(objects, projection, itemsPerPage, locale, entries, 2);
		}
		
		if (entries.size() < 2 || entries.size() > 50) {
			entries.clear();
		}
	}
	
	protected void indexStrings2(
			List objects, IProjection projection, 
			int itemsPerPage, String locale, List entries, int prefixLength) {
			
		int 	start = 0;
		int 	current = 0;
		String	lastPrefix = "";
		
		ISchemaModel schemaModel = m_profile.getSchemaModel();
		Model model = m_profile.getModel();
		
		Iterator i = objects.iterator();
		while (i.hasNext()) {
			Resource 	r = (Resource) i.next();
			RDFNode 	value = (RDFNode) projection.getValue(r);
			
			if (value != null) {
				String s =
					value instanceof Literal ?
					((Literal) value).getString() :
					schemaModel.getLabel((Resource) value, locale, true);
					
				String prefix = s.substring(0, Math.min(prefixLength, s.length())).toLowerCase();
				
				if (!prefix.equals(lastPrefix)) {
					if (current > start && lastPrefix.length() > 0) {
						entries.add(new Entry(
							lastPrefix,
							start / itemsPerPage,
							current - start
						));
					}
					
					lastPrefix = prefix;
					start = current;
				}
			}
			current++;
		}
		
		if (current > start && lastPrefix.length() > 0) {
			entries.add(new Entry(
				lastPrefix,
				start / itemsPerPage,
				current - start
			));
		}
	}
}
